package com.onlyxiahui.common.action.description.handler.impl;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.SynthesizingMethodParameter;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPath;
import com.onlyxiahui.common.action.description.DocumentContext;
import com.onlyxiahui.common.action.description.bean.MethodData;
import com.onlyxiahui.common.action.description.bean.ModuleData;
import com.onlyxiahui.common.action.description.bean.ParameterData;
import com.onlyxiahui.common.action.description.bean.PropertyData;
import com.onlyxiahui.common.action.description.bean.ResultData;
import com.onlyxiahui.common.action.description.enums.data.JsonDataType;
import com.onlyxiahui.common.action.description.enums.data.ParameterType;
import com.onlyxiahui.common.action.description.handler.MethodHandler;
import com.onlyxiahui.common.action.description.handler.MethodTitleHandler;
import com.onlyxiahui.common.action.description.handler.ParameterHandler;
import com.onlyxiahui.common.action.description.handler.ResultHandler;
import com.onlyxiahui.common.action.description.util.ActionKeyUtil;
import com.onlyxiahui.common.action.description.util.ActionMd5Util;
import com.onlyxiahui.common.action.description.util.ActionTitleUtil;
import com.thoughtworks.qdox.model.JavaMethod;

/**
 * Description <br>
 * Date 2020-01-07 16:17:59<br>
 * 
 * @author XiaHui [onlovexiahui@qq.com]<br>
 * @since 1.0.0
 */

public abstract class BaseMethodHandler implements MethodHandler {

	/**
	 * 
	 * Description <br>
	 * Date 2020-04-10 14:15:15<br>
	 * 
	 * @param context
	 * @param moduleData
	 * @param method
	 * @param paths
	 * @param combineAction 是否合并路径
	 * @return
	 * @since 1.0.0
	 */
	public MethodData create(
			DocumentContext context,
			ModuleData moduleData,
			Method method,
			List<String> paths,
			boolean combineAction) {

		Class<?> moduleClass = method.getDeclaringClass();
		String methodName = method.getName();
		Parameter[] parameters = method.getParameters();

		String key = "";
		String name = methodName;
		String title = getMethodTitle(context, moduleData, method);
		// String action = "";
		String description = getMethodDescription(context, method);
		String className = moduleClass.getName();

		boolean ignore = context.getIgnoreJudgeBox().ignore(context, method);

		MethodData md = new MethodData();

		String pk = ActionKeyUtil.getKey(parameters);
		key = className + "." + methodName + "_" + pk;

		md.setKey(ActionMd5Util.lower32(key));
		md.setPaths(paths);
		md.setName(name);
		md.setClassName(className);
		md.setTitle(title);
		md.setDescription(description);
		md.setIgnore(ignore);

		if (combineAction) {
			List<String> modulePaths = moduleData.getPaths();
			Set<String> actions = combineAction(context, modulePaths, paths);
			md.setActions(actions);
		}

		for (ResultHandler h : context.getResultHandlers()) {
			if (h.support(context, moduleData, md, method)) {
				ResultData rd = h.handle(context, moduleData, md, method);
				if (null != rd) {
					md.setResult(rd);
					break;
				}
			}
		}
		return md;
	}

	public Set<String> combineAction(DocumentContext context, List<String> modulePaths, List<String> methodPaths) {
		if (null == modulePaths) {
			modulePaths = new ArrayList<>();
		}
		if (null == methodPaths) {
			methodPaths = new ArrayList<>();
		}

		Set<String> actions = new HashSet<>();

		if (modulePaths.isEmpty()) {
			for (String methodPath : methodPaths) {
				String path = context.getPathMatcher().combine("", methodPath);
				if (null != path && !path.isEmpty()) {
					actions.add(path);
				}
			}
		} else {
			for (String modulePath : modulePaths) {
				for (String methodPath : methodPaths) {
					String path = context.getPathMatcher().combine(modulePath, methodPath);
					if (null != path && !path.isEmpty()) {
						actions.add(path);
					}
				}
			}
		}
		return actions;
	}

	protected MethodParameter[] initMethodParameters(Method method, Class<?> clazz) {
		int count = method.getParameterCount();
		MethodParameter[] result = new MethodParameter[count];
		for (int i = 0; i < count; i++) {
			SynthesizingMethodParameter parameter = SynthesizingMethodParameter.forExecutable(method, i);
			GenericTypeResolver.resolveParameterType(parameter, clazz);
			result[i] = parameter;
		}
		return result;
	}

	protected List<ActionMethod> getActionMethodList(Class<?> clazz) {
		List<ActionMethod> list = new ArrayList<>();
		Method[] methods = clazz.getMethods();
		if (null != methods && methods.length > 0) {
			for (Method method : methods) {
				list.add(new ActionMethod(clazz, method));
			}
		}
		return list;
	}

	public String getMethodTitle(DocumentContext context, ModuleData moduleData, Method method) {
		Class<?> moduleClass = method.getDeclaringClass();
		String methodName = method.getName();
		String className = moduleClass.getName();
		Parameter[] parameters = method.getParameters();

		JavaMethod jm = context.getSourceBox().getJavaMethodByName(className, methodName, parameters);
		String comment = "";
		if (null != jm) {
			comment = jm.getComment();
		}
		String title = null;

		for (MethodTitleHandler h : context.getMethodTitleHandlers()) {
			if ((null == title || title.isEmpty()) && h.support(context, moduleData, method, comment)) {
				title = h.handle(context, moduleData, method, comment);
			}
		}

		if (null == title || title.isEmpty()) {
			title = ActionTitleUtil.handle(comment);
		}

		if (null == title || title.isEmpty()) {
			title = methodName;
		}
		return null == title ? "" : title;
	}

	public String getMethodDescription(DocumentContext context, Method method) {
		Class<?> moduleClass = method.getDeclaringClass();
		String methodName = method.getName();
		String className = moduleClass.getName();
		Parameter[] parameters = method.getParameters();

		String description = "";
		JavaMethod jm = context.getSourceBox().getJavaMethodByName(className, methodName, parameters);
		if (null != jm) {
			description = jm.getComment();
		}
		return null == description ? "" : description;
	}

	public List<ParameterData> getMethodParameters(DocumentContext context, ModuleData moduleData, MethodData md, Method method) {
		Class<?> classType = method.getDeclaringClass();
		List<ParameterData> parameters = new ArrayList<>();
		MethodParameter[] mps = initMethodParameters(method, classType);
		for (MethodParameter parameter : mps) {
			for (ParameterHandler ph : context.getParameterHandlers()) {
				if (ph.support(context, moduleData, md, method, parameter)) {
					ParameterData pd = ph.handle(context, moduleData, md, method, parameter);
					if (null != pd) {
						parameters.add(pd);
					}
				}
			}
		}
		return parameters;
	}

	public List<ParameterData> handleRootMethodParameters(List<ParameterData> list) {
		List<ParameterData> parameters = new ArrayList<>();
		if (null != list) {
			int size = list.size();
			Map<String, PropertyData> rootPathMap = new HashMap<>(size);
			Map<String, PropertyData> pathMap = new HashMap<>(size);

			List<ParameterData> bodys = new ArrayList<>();
			for (ParameterData parameterData : list) {
				String type = parameterData.getParameterType();
				if (ParameterType.body.type().equals(type)) {
					bodys.add(parameterData);
					PropertyData propertyData = parameterData.getProperty();
					if (null != propertyData) {
						List<PropertyData> nodes = propertyData.getNodes();
						if (null != nodes) {
							for (PropertyData node : nodes) {
								if (null != node) {
									String name = node.getName();
									if (null != name && !name.isEmpty()) {
										setRoot(rootPathMap, pathMap, node, name);
									}
								}
							}
						}
					}
				} else if (ParameterType.node.type().equals(type)) {
					PropertyData propertyData = parameterData.getProperty();
					if (null != propertyData) {
						String name = propertyData.getName();
						if (null != name && !name.isEmpty()) {
							setRoot(rootPathMap, pathMap, propertyData, name);
						}
					}
				} else {
					parameters.add(parameterData);
				}
			}

			List<PropertyData> nodes = new ArrayList<>();
			for (PropertyData pd : rootPathMap.values()) {
				nodes.add(pd);
			}

			if (!nodes.isEmpty()) {
				if (!bodys.isEmpty()) {
					ParameterData parameterData = bodys.get(0);
					PropertyData propertyData = parameterData.getProperty();
					if (null != propertyData) {
						propertyData.setNodes(nodes);
					} else {
						propertyData = new PropertyData();

						String name = "";
						String description = "";
						String type = JsonDataType.Object.type();
						String className = Object.class.getName();

						propertyData.setName(name);
						propertyData.setType(type);
						propertyData.setDescription(description);
						propertyData.setClassName(className);

						propertyData.setNodes(nodes);
					}
					parameters.add(parameterData);
				} else {

					PropertyData pd = new PropertyData();

					String name = "";
					String description = "";
					String type = JsonDataType.Object.type();
					String className = Object.class.getName();

					pd.setName(name);
					pd.setType(type);
					pd.setDescription(description);
					pd.setClassName(className);

					pd.setNodes(nodes);

					ParameterData root = new ParameterData(ParameterType.body.type(), pd);
					parameters.add(root);
				}
			}
		}
		return parameters;
	}

	public void setRoot(Map<String, PropertyData> rootPathMap, Map<String, PropertyData> pathMap, PropertyData pd, String pathName) {
		JSONObject o = new JSONObject();
		JSONPath.set(o, pathName, "");
		Set<String> keySet = o.keySet();
		for (String key : keySet) {
			Object v = o.get(key);
			if (v instanceof JSONObject) {
				PropertyData superData = pathMap.get(key);
				if (null == superData) {
					superData = new PropertyData();

					String name = key;
					String description = "";
					String type = JsonDataType.Object.type();
					String className = "map";

					superData.setName(name);
					superData.setType(type);
					superData.setDescription(description);
					superData.setClassName(className);

					pathMap.put(key, superData);
				}

				PropertyData data = rootPathMap.get(key);
				if (null == data) {
					data = superData;
					rootPathMap.put(key, data);
				}
				JSONObject jo = (JSONObject) v;
				setNode(key, key, pathMap, jo, superData, pd);
			} else {
				pd.setName(key);
				rootPathMap.put(key, pd);
			}
		}
	}

	public void setNode(String superKey, String superPath, Map<String, PropertyData> pathMap, JSONObject o, PropertyData superData, PropertyData data) {

		Set<String> keySet = o.keySet();
		for (String key : keySet) {
			String path = superPath + "." + key;
			Object v = o.get(key);
			if (v instanceof JSONObject) {
				PropertyData nextSuperData = pathMap.get(path);
				if (null == nextSuperData) {
					String name = key;
					String description = "";
					String type = JsonDataType.Object.type();
					String className = "map";

					nextSuperData = new PropertyData();
					nextSuperData.setName(name);
					nextSuperData.setType(type);
					nextSuperData.setDescription(description);
					nextSuperData.setClassName(className);

					pathMap.put(key, nextSuperData);

					List<PropertyData> nodes = superData.getNodes();
					if (null == nodes) {
						nodes = new ArrayList<>();
						superData.setNodes(nodes);
					}
					nodes.add(nextSuperData);
				}

				JSONObject jo = (JSONObject) v;
				setNode(key, path, pathMap, jo, nextSuperData, data);
			} else {
				data.setName(key);
				List<PropertyData> nodes = superData.getNodes();
				if (null == nodes) {
					nodes = new ArrayList<>();
					superData.setNodes(nodes);
				}
				nodes.add(data);
			}
		}
	}

	protected class ActionMethod {

		private Class<?> actionClass;
		private Method method;
		private MethodParameter[] methodParameters;

		public ActionMethod(Class<?> actionClass, Method method) {
			this.actionClass = actionClass;
			this.method = method;
			initialize(method);
		}

		private void initialize(Method method) {
			methodParameters = initMethodParameters(method);
		}

		private MethodParameter[] initMethodParameters(Method method) {
			int count = method.getParameterCount();
			MethodParameter[] result = new MethodParameter[count];
			for (int i = 0; i < count; i++) {
				SynthesizingMethodParameter parameter = SynthesizingMethodParameter.forExecutable(method, i);
				GenericTypeResolver.resolveParameterType(parameter, this.actionClass);
				result[i] = parameter;
			}
			return result;
		}

		public Class<?> getActionClass() {
			return actionClass;
		}

		public Method getMethod() {
			return method;
		}

		public MethodParameter[] getMethodParameters() {
			return methodParameters;
		}
	}
}
